package com.jaws.core.common.util;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.SecureRandom;

import com.jaws.core.common.exception.JawsCommonExceptionCode;
import com.jaws.core.common.log.Log;
import com.jaws.core.configuration.CommonProperties;
import lombok.extern.slf4j.Slf4j;

/**
 * 雪花算法生成id
 *
 * @author lixin
 * @date 2018-03-29 10:21:18
 * @since v1.0.0
 */
@Slf4j
public class SnowflakeIdOldUtil {

	/**
	 * 基准时间
	 * Thu, 04 Nov 2010 01:42:54 GMT
	 */
	private static long twepoch = 1288834974657L;

	/**
	 * 区域标志位数
	 */
	private final static long regionIdBits = 3L;

	/**
	 * 机器标识位数
	 */
	private final static long workerIdBits = 10L;

	/**
	 * 序列号识位数
	 */
	private final static long sequenceBits = 10L;

	/**
	 * 区域标志ID最大值
	 */
	private final static long maxRegionId = -1L ^ (-1L << regionIdBits);

	/**
	 * 机器ID最大值
	 */
	private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);

	/**
	 * 序列号ID最大值
	 */
	private final static long sequenceMask = -1L ^ (-1L << sequenceBits);

	/**
	 * 机器ID偏左移10位
	 */
	private final static long workerIdShift = sequenceBits;

	/**
	 * 业务ID偏左移20位
	 */
	private final static long regionIdShift = sequenceBits + workerIdBits;

	/**
	 * 时间毫秒左移23位
	 */
	private final static long timestampLeftShift = sequenceBits + workerIdBits + regionIdBits;

	private static long lastTimestamp = -1L;

	private static long sequence = 0L;

	/**
	 * 第几个机器，数值不可用超过1023，从0开始，最多可以扩到第1024个机器
	 */
	private static long workerId = 0L;

	/**
	 * 第几个机房 ，数值不可用超过7，从0开始，最多可以扩到8个机房
	 */
	private static long datacenterId = 0L;

	/**
	 * 默认不启用雪花算法
	 */
	private static boolean isUsed = false;

	public static void init(CommonProperties commonProperties) {
		log.info(Log.op("SnowflakeIdUtil.init").kv("commonProperties.getSnowflakeMode()",commonProperties.getSnowflakeMode()).toString());
		try {
			switch (commonProperties.getSnowflakeMode()) {
				case HOST:
					workerId = generateWorkerIdByHost();
					break;
				case IPV4:
					workerId = generateWorkerIdByIpv4();
					break;
				case CONFIG:
					workerId = commonProperties.getSnowflakeWorkerId();
					datacenterId = commonProperties.getSnowflakeCenterId();
					break;
				default:
					workerId = generateWorkerIdByHost();
					break;
			}
		} catch (Exception e) {
			throw JawsCommonExceptionCode.SNOWFLAKE_CENTERID_ERROR.exp(e);
		}

		if (workerId > maxWorkerId) {
			throw JawsCommonExceptionCode.SNOWFLAKE_CENTERID_ERROR.exp();
		}

		if (datacenterId > maxRegionId) {
			throw JawsCommonExceptionCode.SNOWFLAKE_CENTERID_ERROR.exp();
		}

		isUsed = true;
		log.info(Log.op("SnowflakeIdUtil.init").kv("isUsed",isUsed).toString());
	}

	public static long generate() {
		if (!isUsed) {
			throw JawsCommonExceptionCode.SNOWFLAKE_SWITH_OFF.exp();
		}
		return nextId(false, 0);
	}

	/**
	 * 实际产生代码的
	 *
	 * @param isPadding
	 * @param busId
	 * @return
	 */
	private static synchronized long nextId(boolean isPadding, long busId) {

		long timestamp = timeGen();
		long paddingnum = datacenterId;

		if (isPadding) {
			paddingnum = busId;
		}

		if (timestamp < lastTimestamp) {

			long delay = lastTimestamp - timestamp;

			// 如果时间偏差较小则等待
			if (delay < 5) {

				try {
					Thread.sleep(delay);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}

				timestamp = timeGen();
			}

			// 如果还没好则抛异常(后续加报警代码实现)
			if (timestamp < lastTimestamp) {
				try {
					throw new Exception("Clock moved backwards.  Refusing to generate id for " + (lastTimestamp - timestamp) + " milliseconds");
				} catch (Exception e) {
					e.printStackTrace();
				}
			} else {
				return generate();
			}

		}

		// 如果上次生成时间和当前时间相同,在同一毫秒内
		if (lastTimestamp == timestamp) {
			// sequence自增，因为sequence只有10bit，所以和sequenceMask相与一下，去掉高位
			sequence = (sequence + 1) & sequenceMask;
			// 判断是否溢出,也就是每毫秒内超过1024，当为1024时，与sequenceMask相与，sequence就等于0
			if (sequence == 0) {
				// 自旋等待到下一毫秒
				timestamp = tailNextMillis(lastTimestamp);
			}
		} else {
			// 如果和上次生成时间不同,重置sequence，就是下一毫秒开始，sequence计数重新从0开始累加,
			// 为了保证尾数随机性更大一些,最后一位设置一个随机数
			sequence = new SecureRandom().nextInt(10);
		}

		lastTimestamp = timestamp;

		return ((timestamp - twepoch) << timestampLeftShift) | (paddingnum << regionIdShift) | (workerId << workerIdShift) | sequence;
	}

	// 防止产生的时间比之前的时间还要小（由于NTP回拨等问题）,保持增量的趋势.
	private static long tailNextMillis(final long lastTimestamp) {
		long timestamp = timeGen();
		while (timestamp <= lastTimestamp) {
			timestamp = timeGen();
		}
		return timestamp;
	}

	// 获取当前的时间戳
	protected static long timeGen() {
		return System.currentTimeMillis();
	}

	/**
	 * ip后两个网段计算，要求前面网段一致
	 *
	 * @return
	 * @author maojian
	 * @date 2018-03-16 10:37:52
	 * @since v1.0.0
	 */
	private static long generateWorkerIdByIpv4() {
		Long workerId;

		InetAddress address;

		try {
			// address = InetAddress.getByName("172.29.1.5");
			address = InetAddress.getLocalHost();
		} catch (final UnknownHostException e) {
			throw JawsCommonExceptionCode.SNOWFLAKE_INETADDRESS_ERROR.exp();
		}
		byte[] ipAddressByteArray = address.getAddress();

		workerId = (long) (((ipAddressByteArray[ipAddressByteArray.length - 2] & 0B11) << Byte.SIZE) + (ipAddressByteArray[ipAddressByteArray.length - 1] & 0xFF));

		return workerId;
	}

	/**
	 * 根据hostname，取“-”后的数字
	 *
	 * @return
	 * @author maojian
	 * @date 2018-03-16 10:40:09
	 * @since v1.0.0
	 */
	private static long generateWorkerIdByHost() {
		InetAddress address;
		Long workerId;
		try {
			address = InetAddress.getLocalHost();
		} catch (final UnknownHostException e) {
			throw new IllegalStateException("Cannot get LocalHost InetAddress, please check your network!");
		}
		String hostName = address.getHostName();
		log.info(Log.op("SnowflakeIdUtil.generateWorkerIdByHost").kv("hostName", hostName).toString());

		try {
			String lastHostName = hostName.substring(hostName.lastIndexOf("-") + 1);
			log.info(Log.op("SnowflakeIdUtil.generateWorkerIdByHost").kv("lastHostName", lastHostName).toString());

			workerId = Long.valueOf(lastHostName);
			log.info(Log.op("SnowflakeIdUtil.generateWorkerIdByHost").kv("workerId", workerId).toString());
		} catch (final NumberFormatException e) {
			throw new IllegalArgumentException(String.format("Wrong hostname:%s, hostname must be end with number!", hostName));
		}

		return workerId;
	}

	public static void main(String[] args) throws UnknownHostException {
		InetAddress address = InetAddress.getByName("255.255.255.254");

		byte[] ipAddressByteArray = address.getAddress();

		workerId = (long) (((ipAddressByteArray[ipAddressByteArray.length - 2] & 0xFF) << Byte.SIZE) + (ipAddressByteArray[ipAddressByteArray.length - 1] & 0xFF));

		System.out.println(workerId);
	}

}
